﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MBTNavigatorSDKFeedPlugin.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge.Plugins
{
    #region Using Directives

    using System.Collections.Generic;

    using MBTQUOTELib;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Events;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;
    using Org.OpenTrader.Framework.LiveObjects;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// The mbt navigator sdk feed plugin.
    /// </summary>
    [Credits("MBT Navigator Feed Plugin", "Handles feed operations using MBT Navigator SDK (MbtComMgr Interop)", "David Denis", 
        "Copyright (C) David Denis <mailto:david.denis@systemathics.com>")]
    [ImplementsInterface("Org.OpenTrader.Framework.Forge.Interfaces.IFeedPlugin")]
    [ConfigurationField("Account", typeof(ConfigurationField.Int), "Account Identifier", IsMandatory = true, DefaultValue = 33002687)]
    [ConfigurationField("Port", typeof(ConfigurationField.Int), "Port to connect to", IsMandatory = true, DefaultValue = 5020)]
    [ConfigurationField("User", typeof(ConfigurationField.String), "User under which to connect", IsMandatory = true, DefaultValue = "DEMOXDZK")]
    [ConfigurationField("Password", typeof(ConfigurationField.String), "Password of the user", IsMandatory = true, DefaultValue = "1book2mice")]
    [ConfigurationField("UseMarketParticipants", typeof(ConfigurationField.Bool), "Use market participants", IsMandatory = true, DefaultValue = false)]
    [ConfigurationField("SubscriptionConfigurationSchema", "Symbol", typeof(ConfigurationField.String), "Symbol needed to register", IsMandatory = true, 
        DefaultValue = "SYMBOL")]
    public sealed class MBTNavigatorSDKFeedPlugin : FeedPlugin
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        /// <summary>
        /// The subscription mapper.
        /// </summary>
        private readonly SubscriptionMappingStore<string> subscriptionMappingStore = new SubscriptionMappingStore<string>();

        /// <summary>
        /// Used to normalize MMID, see below
        /// </summary>
        private static char[] numbers = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

        /// <summary>
        /// MBTNavigatorSDKConnection implements IConnection and some other things
        /// </summary>
        private MBTNavigatorSDKConnection connection;

        /// <summary>
        /// Notifiers per symbol
        /// </summary>
        private IDictionary<string, MbtQuotesNotify> mbtQuotesNotifiers;

        /// <summary>
        /// Use OTC quotes ?
        /// </summary>
        private bool useMarketParticipants;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MBTNavigatorSDKFeedPlugin"/> class.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        public MBTNavigatorSDKFeedPlugin(ILiveObjectDescriptor descriptor, ILiveObjectContainer container)
            : base(descriptor, container)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets ConnectionStatus.
        /// </summary>
        public override EConnectionStatus ConnectionStatus
        {
            get
            {
                return this.connection.ConnectionStatus;
            }
        }

        /// <summary>
        /// Gets ConnectionURL.
        /// </summary>
        public override string ConnectionURL
        {
            get
            {
                return this.connection.ToString();
            }
        }

        /// <summary>
        /// Gets EmitedEventsType.
        /// </summary>
        public override EEmitedEventsType EmitedEventsType
        {
            get
            {
                return EEmitedEventsType.QuoteEvents;
            }
        }

        /// <summary>
        /// Gets a value indicating whether NeedResubscriptionIfDisconnected.
        /// </summary>
        public override bool NeedResubscriptionIfDisconnected
        {
            get
            {
                return true;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The connect.
        /// </summary>
        /// <returns>
        /// </returns>
        public override Status Connect()
        {
            return this.connection.Connect();
        }

        /// <summary>
        /// The disconnect.
        /// </summary>
        /// <returns>
        /// </returns>
        public override Status Disconnect()
        {
            this.subscriptionMappingStore.Clear();
            this.mbtQuotesNotifiers.Clear();

            return this.connection.Disconnect();
        }

        /// <summary>
        /// Called when object is activated
        /// </summary>
        /// <param name="sender">
        /// The sender of event
        /// </param>
        /// <param name="activationEventArgs">
        /// The activation Context argument
        /// </param>
        public override void OnActivation(object sender, ActivationEventArgs activationEventArgs)
        {
            if (activationEventArgs is ActivationEventArgs.CreateDestroy)
            {
                switch (activationEventArgs.Type)
                {
                    case EActivationType.Activation:
                        this.mbtQuotesNotifiers = new Dictionary<string, MbtQuotesNotify>();
                        this.useMarketParticipants = (bool)this.Configuration["UseMarketParticipants"];
                        this.connection = new MBTNavigatorSDKConnection(
                            (int)this.Configuration["Account"], (string)this.Configuration["User"], (string)this.Configuration["Password"], this);

                        break;

                    case EActivationType.Desactivation:
                        break;
                }
            }
        }

        /// <summary>
        /// Send a "start subscription" call
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <returns>
        /// True on success, False otherwise.
        /// </returns>
        public override Status StartSubscription(IFeedWiring wiring, RemotableEventHandler<FeedEventArgs> handler)
        {
            // No Connection available
            if (this.connection.ConnectionStatus != EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "StartSubscription: Connection not opened");
            }

            // Already some subscription
            if (this.subscriptionMappingStore.HasMapping(wiring))
            {
                return Status.Failure(Identity, "StartSubscription: Already subscribed");
            }

            // What symbol to subscribe
            var symbol = wiring.SubscriptionConfiguration["Symbol"];
            if (symbol == null)
            {
                return Status.Failure(Identity, "StartSubscription: 'Symbol' parameter is null");
            }

            // Add request entries
            this.subscriptionMappingStore.SetMapping(wiring, symbol);

            // Subscribe
            this.mbtQuotesNotifiers[symbol] = new MbtQuotesNotify(this, handler);
            this.connection.MbtComMgr.Quotes.AdviseSymbol(
                this.mbtQuotesNotifiers[symbol], symbol, (int)(enumQuoteServiceFlags.qsfLevelTwo | enumQuoteServiceFlags.qsfLevelOne));

            // All done
            return Status.Success(Identity, "StartSubscription: Subscription message sent");
        }

        /// <summary>
        /// Send a "stop subscription" call
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <returns>
        /// True on success, False otherwise.
        /// </returns>
        public override Status StopSubscription(IFeedWiring wiring, RemotableEventHandler<FeedEventArgs> handler)
        {
            // No Connection available
            if (this.connection.ConnectionStatus != EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "StopSubscription: Connection not opened");
            }

            // Not subscribed
            if (!this.subscriptionMappingStore.HasMapping(wiring))
            {
                return Status.Failure(Identity, "StopSubscription: Not subscribed");
            }

            // Symbol 
            var symbol = wiring.SubscriptionConfiguration["Symbol"];

            // Unsubscribe
            this.connection.MbtComMgr.Quotes.UnadviseSymbol(
                this.mbtQuotesNotifiers[symbol], symbol, (int)(enumQuoteServiceFlags.qsfLevelTwo | enumQuoteServiceFlags.qsfLevelOne));
            this.mbtQuotesNotifiers.Remove(symbol);

            // Remove requests
            this.subscriptionMappingStore.UnsetMapping(wiring, symbol);

            // All done
            return Status.Success(Identity, "StopSubscription: Unsubscription message sent");
        }

        #endregion

        #region Methods

        /// <summary>
        /// The process level 1.
        /// </summary>
        /// <param name="quoteRecord">
        /// The quote record.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        private void ProcessLevel1(ref QUOTERECORD quoteRecord, RemotableEventHandler<FeedEventArgs> handler)
        {
            // Stdout.WriteLine("Level1:" + quoteRecord.lBidSize + " " + quoteRecord.dBid + " ; " + quoteRecord.dAsk + " " + quoteRecord.lAskSize);
        }

        /// <summary>
        /// The process level 2.
        /// </summary>
        /// <param name="quoteRecord">
        /// The quote record.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        private void ProcessLevel2(ref LEVEL2RECORD quoteRecord, RemotableEventHandler<FeedEventArgs> handler)
        {
            if (quoteRecord.bstrSource == null)
            {
                return;
            }

            var instrumentID = this.subscriptionMappingStore.GetMapping(quoteRecord.bstrSymbol);
            var mmid = quoteRecord.bstrMMID;

            // Forex quotes (All Levels)
            if (mmid.IndexOf("TDFX") >= 0)
            {
                mmid = mmid.Replace("TDFX", "TDFX:");
            }
            else if (mmid.IndexOfAny(numbers) < 0)
            {
                // One Level only quote type workaround
                if (! this.useMarketParticipants)
                {
                    return;
                }

                mmid = string.Format("{0}:00000000{1}", mmid, quoteRecord.side);

                // Just add a "side" to make it a unique key, those participants can only contribute once on each side
            }

            var pos = mmid.IndexOf(':');

            // Normalization failed
            if (pos <= 0)
            {
                return;
            }

            var quoteKey = mmid;
            var quoteContributor = mmid.Substring(0, pos);
            var quoteSide = quoteRecord.side == enumMarketSide.msBid ? ESide.Bid : ESide.Ask;
            var closed = quoteRecord.bClosed != 0;
            if (quoteRecord.lSize == 0)
            {
                closed = true;
            }

            if (quoteRecord.dPrice == 0)
            {
                closed = true;
            }

            if (closed)
            {
                // Is a "closing" quote
                this.RaiseEventMarketQuotes(new[] { MarketQuote.ClosingQuote(quoteKey, Timings.Now, quoteSide) }, false, handler);
            }
            else
            {
                // Is a "non closing" quote
                IContribution contribution = new Contribution(quoteKey, quoteContributor, quoteRecord.lSize);
                this.RaiseEventMarketQuotes(
                    new[] { MarketQuote.Quote(quoteKey, Timings.Now, quoteSide, quoteRecord.dPrice, quoteRecord.lSize, contribution) }, false, handler);
            }
        }

        /// <summary>
        /// The process ts.
        /// </summary>
        /// <param name="record">
        /// The record.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        private void ProcessTS(ref TSRECORD record, RemotableEventHandler<FeedEventArgs> handler)
        {
        }

        #endregion

        /// <summary>
        /// The mbt quotes event sink.
        /// </summary>
        internal class MbtQuotesNotify : IMbtQuotesNotify
        {
            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="MbtQuotesNotify"/> class.
            /// </summary>
            /// <param name="plugin">
            /// The plugin.
            /// </param>
            /// <param name="handler">
            /// The handler.
            /// </param>
            public MbtQuotesNotify(MBTNavigatorSDKFeedPlugin plugin, RemotableEventHandler<FeedEventArgs> handler)
            {
                this.PluginInstance = plugin;
                this.Handler = handler;
            }

            #endregion

            #region Properties

            /// <summary>
            /// Gets Handler.
            /// </summary>
            public RemotableEventHandler<FeedEventArgs> Handler { get; private set; }

            /// <summary>
            /// Gets or sets this.PluginInstance.
            /// </summary>
            private MBTNavigatorSDKFeedPlugin PluginInstance { get; set; }

            #endregion

            #region Implemented Interfaces

            #region IMbtQuotesNotify

            /// <summary>
            /// The on level 2 data.
            /// </summary>
            /// <param name="quoteRecord">
            /// The p rec.
            /// </param>
            public void OnLevel2Data(ref LEVEL2RECORD quoteRecord)
            {
                this.PluginInstance.ProcessLevel2(ref quoteRecord, this.Handler);
            }

            /// <summary>
            /// The on options data.
            /// </summary>
            /// <param name="quoteRecord">
            /// The p rec.
            /// </param>
            public void OnOptionsData(ref OPTIONSRECORD quoteRecord)
            {
            }

            /// <summary>
            /// The on quote data.
            /// </summary>
            /// <param name="quoteRecord">
            /// The p rec.
            /// </param>
            public void OnQuoteData(ref QUOTERECORD quoteRecord)
            {
                this.PluginInstance.ProcessLevel1(ref quoteRecord, this.Handler);
            }

            /// <summary>
            /// The on ts data.
            /// </summary>
            /// <param name="quoteRecord">
            /// The p rec.
            /// </param>
            public void OnTSData(ref TSRECORD quoteRecord)
            {
                this.PluginInstance.ProcessTS(ref quoteRecord, this.Handler);
            }

            #endregion

            #endregion
        }
    }
}